package ch.zhaw.cctd.domain.creep;

import java.awt.Dimension;
import java.awt.Point;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.zhaw.cctd.domain.GraphicalElement;
import ch.zhaw.cctd.domain.Movable;
import ch.zhaw.cctd.domain.Shootable;
import ch.zhaw.cctd.domain.map.MapElement;
import ch.zhaw.cctd.domain.map.WayPoint;
import ch.zhaw.cctd.domain.prototype.CreepPrototype;

/**
 * Representiert ein Creep auf der Map. Zuständig für dessen Zustand.
 * 
 * @author Rolf
 */
public class Creep extends MapElement implements Shootable, GraphicalElement, Movable, Serializable {

	private static final long serialVersionUID = -2280852597698565327L;

	/**
	 * Logger
	 */
	private static final Logger logger = LoggerFactory.getLogger(Creep.class);

	/**
	 * Layer Level the Creep is painted on
	 */
	private static final int CREEP_LAYER_LEVEL = 0;

	// Creep Properties (Fixed)
	private List<String> abilities;
	private String id;
	private int life;
	private String title;
	private int creepMoney;

	// Movement
	private int speed;
	private WayPoint pathCurrentNode;

	// private WayPoint currentPosition;
	private long startTime;

	// Creep Status (Changeable
	private int currentLife;
	private List<Debuff> debuffList;

	/**
	 * Main Constructor
	 * 
	 * @param wayPoint
	 * @param creepPrototype
	 */
	public Creep(WayPoint wayPoint, CreepPrototype creepPrototype) {
		// MapElement
		super(wayPoint.getLocation(), new Dimension(creepPrototype.getSize(), creepPrototype.getSize()), creepPrototype.getImagePath(), CREEP_LAYER_LEVEL);

		// Debuffs
		this.debuffList = new ArrayList<Debuff>();
		// Abilities
		this.setAbilities(creepPrototype.getAbilities());
		// Creep Settings
		this.setId(creepPrototype.getId());
		this.setTotalLife(creepPrototype.getLife());
		this.setSpeed(creepPrototype.getSpeed());
		this.setTitle(creepPrototype.getTitle());
		this.setCreepMoney(creepPrototype.getMoney());

		// Creep Status
		this.setCurrentLife(getTotalLife());
		// Movement
		this.setOriginPoint(wayPoint);
		this.setNewCenter(pathCurrentNode);
		this.startTime = 0;

	}

	// ++++++++++++++++++++MOVEMENT STUFF++++++++++++++++++++
	/**
	 * Tell the Creep to start Moving from now on. Next move() will calculate
	 * the position is has reached from the moment startMoving was called.
	 */
	public void startMoving() {
		startTime = Calendar.getInstance().getTimeInMillis();
	}

	/**
	 * Calculate the current Position of the Creep.
	 * 
	 * @return
	 */
	public void move() {
		logger.trace("MOVE CREEP");
		if (isAlive()) {
			//logger.trace("Current Position: " + getLocation().toString() + " Center Point: " + getCenter().toString());
			//logger.trace("Creep is alive");
			validateDebuffs();
			// Is already Moving, Calculating Position
			long nowTime = Calendar.getInstance().getTimeInMillis();
			// How far did we walk on the current path ? If Factor is larger
			// than 1 we have crossed the next Waypoint.
			double factor;

			int looper = 0;

			// Check until we found the current Path we are on.
			do {
				logger.trace("Start Calculation of new Factor");

				double wayLength = (nowTime - startTime) * getSpeed() / 1000;
				logger.trace("Way Moving is {} ({})", wayLength, (nowTime - startTime));
				double totalWayLength = getCenter().distance(pathCurrentNode.getTargetPoint());
				logger.trace("Total Way Length  is {}", totalWayLength);
				factor = wayLength / totalWayLength;

				if (factor >= 1.0) {
					logger.trace("Factor is bigger or equal to 1");
					startTime = Calendar.getInstance().getTimeInMillis();
					// Origin Position Handling
					setOriginPoint(pathCurrentNode.getTargetPoint());

					// CurrentPosition Handling
					setNewCenter(pathCurrentNode);
					logger.trace("New Center set {}", getCenter());
				}
			} while (factor > 1.0 && looper++ < 100);
			if (looper >= 100) {
				logger.error("endless loop while moving");
				throw new IllegalStateException("Endless Loop while calculating the position of a Creep.");
			}

			// Calulate X and Y of the current path.
			if (factor < 1.0) {
				logger.trace("New Factor Calculated");
				// Zielpunkt = Startpunkt + faktor*(Endpunkt-Startpunkt)
				double xCord = getCenter().getX() + factor * (pathCurrentNode.getTargetPoint().getX() - getCenter().getX());
				double yCord = getCenter().getY() + factor * (pathCurrentNode.getTargetPoint().getY() - getCenter().getY());
				Point newCenterPoint = new Point((int) xCord, (int) yCord);
				setNewCenter(newCenterPoint);
				logger.trace("New Centerpoint is: {}", newCenterPoint);
				startTime = nowTime;

			}
		} else {
			startTime = 0;
		}
	}

	/**
	 * Checks if the Creep is moving
	 * 
	 * @return true/false
	 */
	public boolean isMoving() {
		return startTime != 0;
	}

	/**
	 * Sets the Waypoint the Creep spawning at
	 * 
	 * @param spawnPoint
	 */
	private void setOriginPoint(WayPoint spawnPoint) {
		this.pathCurrentNode = spawnPoint;
	}

	// ++++++++++++++++++++ CREEP DETAILS ++++++++++++++++++++
	/**
	 * Set the Creep ID
	 */
	private void setId(String id) {
		this.id = id;
	}

	/**
	 * Get the Creep ID
	 * 
	 * @return
	 */
	public String getId() {
		return id;
	}

	/**
	 * Set the Creep Title
	 * 
	 * @param title
	 */
	private void setTitle(String title) {
		this.title = title;
	}

	/**
	 * Get the Creep Title
	 * 
	 * @return
	 */
	public String getTitle() {
		return title;
	}

	// ++++++++++++++++++++ ABILITIES STUFF++++++++++++++++++++
	/**
	 * Set the abilities
	 */
	private void setAbilities(List<String> abilities) {
		this.abilities = abilities;
		logger.trace("Setting Creeps Abilities. listSize: {}", this.abilities.size());

	}

	/**
	 * Check if the Creep has an Ability
	 */
	public boolean hasAbility(String ability) {
		logger.trace("Does Creep has Ability:{}?", ability);
		
		boolean containsAbility = abilities.contains(ability);
		
		logger.trace("Creep has Ability:{}", containsAbility);
		// If not: Check if it has been added or force-removed by a debuff
		if (!containsAbility) {
			for (Debuff debuff : debuffList) {
				for (String s : debuff.getRemoveAbilities()) {
					if (s.equals(ability))
						// debuff remove overrides an add
						return false;
				}
				for (String s : debuff.getAddAbilities()) {
					if (s.equals(ability))
						containsAbility = true;
				}
			}
		}
		// If yes: Check if it has been removed by a Debuff
		else {
			for (Debuff debuff : debuffList) {
				for (String s : debuff.getRemoveAbilities()) {
					if (s.equals(ability))
						containsAbility = false;
				}
			}
		}

		return containsAbility;
	}

	// ++++++++++++++++++++ DEBUFF STUFF ++++++++++++++++++++
	/**
	 * Removes a specified Debuff from the List
	 */
	private void removeDebuff(Debuff debuff) {
		debuffList.remove(debuff);
	}

	/**
	 * Checks all Debuffs if they are finished and removes them if finished
	 */
	private void validateDebuffs() {
		long nowTime = Calendar.getInstance().getTimeInMillis();
		List<Debuff> tempDebuffList = new ArrayList<Debuff>(debuffList);
		for (Debuff debuff : tempDebuffList) {
			if (debuff.getTimeoutTime() < nowTime) {
				// Debuff has run out of Time
				removeDebuff(debuff);
			}
		}
	}

	// ++++++++++++++++++++SPEED STUFF++++++++++++++++++++
	/**
	 * Calculates the current Speed and returns it
	 * 
	 * @return speed
	 */
	public int getSpeed() {
		int currentSpeed = speed;

		for (Debuff debuff : debuffList) {
			currentSpeed += debuff.getModifyerSpeed();
		}
		return currentSpeed;
	}

	/**
	 * Sets the Creep Normal Speed
	 * 
	 * @param speed
	 */
	private void setSpeed(int speed) {
		this.speed = speed;
	}

	// ++++++++++++++++++++LIFE STUFF++++++++++++++++++++
	/**
	 * Set the Creep Total Life
	 * 
	 * @param life
	 */
	private void setTotalLife(int life) {
		this.life = life;
	}

	/**
	 * Returns the Creep Total life
	 */
	public int getTotalLife() {
		return life;
	}

	/**
	 * Set the Creep Current Life
	 * 
	 * @param currentLife
	 */
	private void setCurrentLife(int currentLife) {
		this.currentLife = currentLife;
	}

	/**
	 * Damage the creep and return remaining life
	 * 
	 * @param damage
	 * @return remaining life
	 */
	public int damage(int damage) {
		logger.trace("Creep Damaged with {}", damage);
		setCurrentLife(getCurrentLife() - damage);
		if (!isAlive()) {
			logger.trace("Creep dies");
		}
		return getCurrentLife();
	}

	/**
	 * Returns the Current Life
	 */
	public int getCurrentLife() {
		return currentLife;
	}

	/**
	 * Checks if a Creep is still alive
	 */
	public boolean isAlive() {
		return (getCurrentLife() > 0);
	}

	// ++++++++++++++++++++MONEY STUFF++++++++++++++++++++
	/**
	 * Sets the Creep Worth
	 * 
	 * @param money
	 */
	private void setCreepMoney(int creepMoney) {
		this.creepMoney = creepMoney;
	}

	/**
	 * Return the Creep Worth
	 * 
	 * @return money
	 */
	public int getCreepMoney() {
		return this.creepMoney;
	}

	/**
	 * Collect the Money from the Creep and set it to 0
	 * 
	 * @return Money
	 */
	synchronized public int payCreepMoney() {
		logger.debug("Pay: {}", this.creepMoney);
		int tempCreepMoney = this.creepMoney;
		setCreepMoney(0);
		return tempCreepMoney;
	}

	/**
	 * Adds the debuffs
	 */
	public void addDebuffs(Debuff... debuffs) {
		for (Debuff d : debuffs) {
			this.debuffList.add(d);
		}
	}
	
	public void addDebuff(Debuff debuff) {
		this.debuffList.add(debuff);
	}

}
